import { defineAsyncComponent, FunctionalComponent, CSSProperties } from 'vue'
import { AppLoading } from '@/components/Application'

declare interface Fn<T = any, R = T> {
  (...arg: T[]): R
}

interface Options {
  delay?: number
  timeout?: number
  loading?: boolean
  retry?: boolean
}

// 通过函数式组件定义Loading组件
const Loading: FunctionalComponent = () => {
  const style: CSSProperties = {
    width: '30px',
    height: '30px'
  }
  return (
    <div style={style}>
      <AppLoading />
    </div>
  )
}

/*
  创建异步组件，如果我们的项目过大了，
  对于某些组件我们希望通过异步的方式来进行加载
  目的是可以对其进行分包处理，可以优化首屏的渲染速度
*/
export function createAsyncComponent(loader: Fn, options: Options = {}) {
  const { delay = 100, timeout = 30000, loading = false, retry = true } = options
  return defineAsyncComponent({
    // 工厂函数
    loader,
    // 加载过程中显示的组件
    loadingComponent: loading ? <Loading /> : undefined,
    // 加载失败时显示的组件
    errorComponent: Error,
    // 如果超时，将显示error组件
    timeout,
    // 在显示 loadingComponent 之前的延时 | 默认值：200 (单位：ms)
    delay,
    /**
     *
     * @param {*} error Error message object
     * @param {*} retry 函数, 调用retry尝试重新加载
     * @param {*} fail  End of failure
     * @param {*} attempts 记录尝试的次数
     */
    onError: !retry
      ? () => {
          return false
        }
      : (error, retry, fail, attempts) => {
          if (error.message.match(/fetch/) && attempts <= 3) {
            // 获取错误时重试3次
            retry()
          } else {
            // Note that retry/fail are like resolve/reject of a promise:
            // one of them must be called for the error handling to continue.
            fail()
          }
        }
  })
}
